#--
# Copyright (c) 2007 Maximilian Ott
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#++


module WBuilder
  module Edit
    
    def _edit_page_body(object_name, object, options, &block)
      raise ArgumentError, "Missing block '#{object_name}'" unless block_given?
      raise ArgumentError, "Missing object '#{object_name}'" if @object.nil?
#  puts "EDIT_BODY #{object_id}>> #{@object_name}"
      @content.div(:class => "wedit") do
        hopts = options.delete(:html) || {}
        hopts[:multipart] = true if options[:includes_uploads]
        @content << form_tag(options[:url] || {}, hopts)
        @content.ul(:class => "wfob") do
          yield 
        end
        text @footer if @footer
        @content << '</form>'
      end
    end
    
    def _edit_fields_for(object_name, *args, &block)
      raise ArgumentError, "Missing block" unless block_given?
      options = args.last.is_a?(Hash) ? args.pop : {}
      object  = args.first || @object.__send__(object_name)
      if object.nil?
        klass = @object.send(object_name.to_s + '_class')
        object = klass.new
      end
      raise ArgumentError, "Missing object '#{object_name}'"  if object.nil?
      if (object.respond_to?(:record_writable?) && !object.record_writable?)
        # can't edit record'
        return
      end
      current_object = @object
      @object = object
#      puts "OBJ0 #{object_id}>>> #{@object_name}"      
      @object_name = @object_name.kind_of?(Array) ? @object_name : [@object_name]
      current_object_name = @object_name.dup
      @object_name << object_name
      index = options.delete :index
      @object_name << index if index
#      puts "OBJ>>> #{@object_name}"
#      block.call(@object) if block

      __add_object_id if options.delete(:add_object_id)
      yield @object, index
      @object_name = current_object_name
      @object = current_object
      nil
    end

    # Add a hidden field holding the ID of this component
    def __add_object_id
      if (@object && (id = @object.id))
        p = @object_name[0]
        s = @object_name.slice(1..-1).join('][')
        n = s.empty? ? p : "#{p}[#{s}]"
        name = n + '[id]'
        @content.input(:name => name, :type => 'hidden', :value => id.to_s)
      end
    end
    
    #
    # Options:
    #   * min_count - Min. number of records to show, stuff with empty ones if necessary
    #
    def _edit_array_for(object_name, *args, &block)
      raise ArgumentError, "Missing block" unless block_given?
      options = args.last.is_a?(Hash) ? args.pop : {}
      
#      if options[:editable?] == false
#        mode = @mode
#        mode = :show
#        __edit_wrapper(object_name, options, false) do
#          _show_array_for(object_name, *args, &block)
#        end
#        @mode = mode
#        return
#      end
      
#      rec_array  = args.empty? ? @object.send(object_name) : args.first
      rec_array= args.first || @object.__send__(object_name)
      puts "EA=> size: #{rec_array.size}"
#      @n = object_name; @r = rec_array; @a = args; breakpoint
      # Make sure that a min. number of records being shown (create them if necessary)
      min_count = options[:min_count] || 1
      if (min_count > 0)
        el_class = @object.send(object_name.to_s + '_class') # class name of what needs to be created.
        rec_array = rec_array.dup
        (min_count - rec_array.size).times do rec_array << el_class.new end
      end
      
      ul_id = __unique_id
      opt = options.merge(:wrapper => :ul, 
        :wrapper_class => "#{object_name} rec_array", :wrapper_id => ul_id)
      i = 1
      #__edit_wrapper(object_name, opt, false) do |builder|
      __edit_wrapper(nil, opt, false) do |builder|
        rec_array.each do |r|
#          puts "aa-> #{r} #{r.class} #{object_name}  b:#{builder.object_id}"
          #@content << this.fields_for(object_name, r, options.merge(:index => i), &block)
#          if (id = r.id)
#            
#          end
          
          _edit_fields_for(object_name, r, options.merge(:index => i, :add_object_id => 1), &block)
          i += 1
        end
        nil
      end
  
      if (rec_array[0].respond_to?(:record_writable?) && !rec_array[0].record_writable?)
        # can't add new record'
        return nil
      end

      # store the extension 
      html = capture do
        _edit_fields_for(object_name, rec_array[0].class.send(:new), options.merge(:index => '######'), &block)
      end

      if @object_name.kind_of?(Array)
        n = @object_name.join('_')
      else
        n = @object_name.to_s
      end
      n = n + '_' + object_name.to_s
#      breakpoint
      s = @action_view.controller.session[:dyn_add] ||= {}
      sc = s[@action_view.controller.controller_class_name] ||= {}
      sc[n] = [i, ul_id, html.to_s]

#      puts sc[n].inspect
#      puts "==>> #{@template.controller.controller_class_name.class} #{i.class} #{ul_id.class} #{html.class}"

      name = object_name.to_s.singularize
      @content << @action_view.link_to_remote("Add #{name} ...", 
        :url => {:action => "add_extension"},
        :controller => @action_view.controller,
        :with => "'p=#{n}'",
        :method => 'get')
        
      nil
    end
    
    def array_index
      @options[:index] || 0
    end
    
    [:file_field, :password_field, :text_area].each do |meth|
      define_method "_edit_#{meth}" do | method, *args |
        options = args.last.is_a?(Hash) ? args.pop : {}
        __edit_wrapper(method, options) do
#          puts "EL>>> #{options[:class]}"
          @action_view.__send__(meth, method, options)
        end
      end
    end
    
    def _edit_container(options = {}, &block)
      __edit_wrapper(nil, options, &block)
    end
    
    def _edit_text_field(method, options={})
      __edit_wrapper(method, options) do
#        puts "TF>>> #{@object_name} :: #{method} :: #{options.inspect}"
#        @action_view.text_field(method, options.merge(:class => "field text large"))
        it = WFormInstanceTag.new(@object_name, method, self, @content, nil, @object)
        opts = options[:html] || {}
        opts.merge! options[:html_edit] || {}
        it.to_input_field_tag('text', opts.reverse_merge(:class => "field text large"))
#        @action_view.text_field(method, {})
      end
    end
    
    def _edit_check_box(method, options = {})
      __edit_wrapper(method, options) do |builder|
        it = WFormInstanceTag.new(@object_name, method, self, @content, nil, @object)
        puts it.to_check_box_tag(options[:html] || {})
        it.to_check_box_tag(options[:html] || {})        
#        it.tag('checkbox', options[:html] || {})
      end
    end
    
    def _edit_radio_button(method, tag_value, options = {})
      __edit_wrapper(method, options) do |builder|
        it = WFormInstanceTag.new(@object_name, method, self, @content, nil, @object)
        it.to_radio_button_tag(tag_value, options[:html] || {})
      end
    end
    
    def _edit_date_select(method, options = {})
      __edit_wrapper(method, options) do |builder|
        it = WFormInstanceTag.new(@object_name, method, self, @content, nil, @object)
        it.to_date_select_tag(options[:html] || {})
      end
    end
  
    def _edit_radio_selection(method, choices, options = {})
      options = options.reverse_merge({:wrapper => :nothing})
      name = method.to_s
      this = self
  #    breakpoint
      __edit_wrapper(method, options.merge(:value_choices => choices), false) do
        choices.each do |c|
          opt = options.merge({:supress_list => 1, :label_pos => :right, :wrapper => :span})
          if c.kind_of? Array
            label = c[0]
            value = c[1]
          else
            value = c
            label = c.kind_of?(Symbol) ? c.to_s.capitalize : c
          end
          _edit_radio_button(method, value, opt.merge({:label => label}))
        end
      end
    end
    
    def _edit_selection(method, choices, options = {})
        current = @object.nil? ? nil : @object.send(method)
        if current.blank?
          choices = choices.dup.insert(0, ['... Select', ''])
        end
      __edit_wrapper(method, options.merge(:value_choices => choices)) do
        it = WFormInstanceTag.new(@object_name, method, self, @content, nil, @object)
        it.to_generic_tag('select', options[:html]) do
          res = @action_view.options_for_select(choices, current)
          text res
        end
        nil # prevent spilling over of option
      end
    end
    
    def _edit_upload_field(method, options = {}) 
      __edit_wrapper(method, options) do
        it = WFormInstanceTag.new(@object_name, method, self, @content, nil, @object)
        opts = options[:html] || {}
        it.to_input_field_tag('file', opts)
        
#        file_field :document, 'uploaded_doc'
      end
    end

    
    def _edit_footer(&block)
      if block_given?
        @footer = capture do
          yield self
        end
      else
        @footer
      end
    end
    
    ############ COMPOUND ################

    # label, size of field
    NAME_FIELDS = {
      :title => ['Title', 2],
      :first => ['First', 15],
      :family => ['Last', 15],
      :prefered => ['Prefered', 8]
    }
    
    def _edit_name_field(method, fields = [:title, :first, :prefered, :family], options = {})
      options = options.reverse_merge({:label => 'Name', :wrapper => :nothing})
      name = method.to_s
      __edit_wrapper(nil, options, false) do
        opt = options.merge({:supress_list => 1, :label_pos => :bottom, :wrapper => :span})
        fields.each do |f|
          m = name + '_' + f.to_s
          p = NAME_FIELDS[f]
          text_field(m, opt.merge({:label => p[0], :html => {:size => p[1]}}))
        end
      end
    end
  
    def _edit_address_field(method, options = {})
      options = options.reverse_merge({:label => 'Address'})
      name = method.to_s
      __edit_wrapper(nil, options) do
        opt = options.merge({:supress_list => 1, :label_pos => :bottom})
        _edit_text_field(name + "_1", opt.merge({:label => 'Street Address'}))
        _edit_text_field(name + '_2', opt.merge({:label => 'Address Line 2'}))
        
        o = {:label => 'City', :item_pos => :left}
        text_field(name + '_city', opt.merge(o))
        o = {:label => 'State / Province / Region', :item_pos => :right}
        text_field(name + '_state', opt.merge(o))
        
        o = {:label => 'Postal / Zip Code', :item_pos => :left}
        text_field(name + '_postal', opt.merge(o))
        o = {:label => 'Country', :item_pos => :right}
        text_field(name + '_country', opt.merge(o))
      end
    end
  
    def _edit_phone_field(options = {})
      options = options.reverse_merge({:label => 'Phone'})
      __edit_wrapper(:phone, options) do
        opt = options.merge({:supress_list => 1, :label_pos => :bottom})
        _edit_text_field(:phone, opt.merge({:label => 'Phone'}))
        _edit_text_field(:mobile, opt.merge({:label => 'Mobile'}))
      end
    end
    
    def _edit_gender_selector(method, options = {})
      options = options.reverse_merge({:label => 'Gender'})
      _edit_radio_selection(method, [:male, :female], options)
    end
    
    def _edit_section(title, descr = nil, &block)
      @content.li(:class => :section) do
        @content.h3 title
        @content.p(descr) if descr
      end
      yield if block
    end
    
    #
    # options:
    #  * :supress_list => 1
    #  * :item_pos => :center (default), :left, :right
    #  * :label => string
    #  * :label_pos => :top, :bottom, :right
    #
    # If output is false, the result of 'block' is NOT being
    # pushed to markaby. Useful for 'macro' features, such as name_field
    #
    def __edit_wrapper(method, options = {}, output = true, &block)
      puts "EWRAPP >> #{method}"
      if (method)
        if !@object.readable?(method)
          # not visible
          return
        elsif !@object.writable?(method)
          __edit_wrapper(nil, options.merge(:desc_class => 'desc_show', :wrapper => :nothing), true) do
            c = __show_value(method, options)
            if (c && !c.to_s.empty?)
              "<span class='desc_not_editable'>#{c}</span>"
            else
              "<span class='desc_undefined'>undefined</span>"
            end
          end
          return
        end
      end
      
      outer = options.has_key?(:supress_list) ? :nothing : :li
      if options.has_key?(:label)
        lname = options[:label]
      elsif !options.has_key?(:supress_label)
        lname = method.to_s.capitalize
      end
      label_pos = options[:label_pos] || :top
  #    @l = lname; @o = options; breakpoint
      @content.__send__(outer) do 
        if (lname && label_pos == :top)
          @content.label(lname, :class => options[:desc_class] || :desc) 
        end
        #div do
        wrap = options[:wrapper] || :div
        wrapc = options[:wrapper_class]
        if wrapc.nil?
          # the class for psoitioning has a 'f' in front ('fleft', 'fright')
          c = options[:item_pos]
          wrapc = 'f' + c.to_s if c
        end
        o = wrapc ? {:class => wrapc} : {}
        o[:id] = options[:wrapper_id] || __unique_id
        @content.__send__(wrap, o) do
          t = yield(self)
          #puts "yy #{output} -> #{t}"
          text t if output && t
          if (lname && label_pos == :bottom)
            @content.label lname
          end
        end
        if (lname && label_pos == :right)
          @content.span do @content.label(lname) end
        end
      end
    end
  end # module Edit
  
  class WFormInstanceTag < ActionView::Helpers::InstanceTag
    
    def initialize(object_name, method_name, template_object, markaby, local_binding = nil, object = nil)
      @content = markaby
      super(object_name, method_name, template_object, local_binding, object)
    end
    
    def to_generic_tag(tag_name, options = {}, &block)
      options = (options || {}).stringify_keys
      add_default_name_and_id(options)
  #    @content.send(tag_name, options) do
#    puts "FOORM>> #{tag_name}::#{@content.class}::#{options.inspect}"
      @content.select(options) do
        yield
      end
#    puts "FOORM<<"
    end
    
    def to_radio_button_tag(tag_value, options = {})
  #    breakpoint
      options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
      options["type"]     = "radio"
      options["value"]    = tag_value
      if options.has_key?("checked")
        cv = options.delete "checked"
        checked = cv == true || cv == "checked"
      else
        checked = self.class.radio_button_checked?(value(object), tag_value)
      end
      options["checked"]  = "checked" if checked
      pretty_tag_value    = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
      options["id"]     ||= defined?(@auto_index) ?             
        "#{tag_id_with_index(@auto_index)}_#{pretty_tag_value}" :
        "#{tag_id}_#{pretty_tag_value}"
      add_default_name_and_id(options)
      tag("input", options)
    end
    
    def to_file_field_tag(options = {})
      if object.respond_to?("errors") && object.errors.respond_to?("on")
        error_wrapping(tag('file_field', options), object.errors.on(@method_name))
      else
        tag('file_field', options)
      end
    end
    
  end
  
end # module WBuilder

# Overide the methods creating the tags to support the nested fields_for
# method introduced above
#
class ActionView::Helpers::InstanceTag
  def tag_name
    "#{name_prefix}[#{@method_name}]"
  end
  
  def tag_name_with_index(index)
    "#{name_prefix}[#{index}][#{@method_name}]"
  end
  
  def name_prefix
    if @object_name2.kind_of? Array
      p = @object_name2[0]
      s = @object_name2.slice(1..-1).join('][')
      s.empty? ? p : "#{p}[#{s}]"
    else
      @object_name
    end
  end

  def tag_id
    "#{id_prefix}_#{@method_name}"
  end
  
  def tag_id_with_index(index)
    "#{id_prefix}_#{index}_#{@method_name}"
  end

  def id_prefix
    if @object_name2.kind_of? Array
      @object_name2.join('_')
    else
      @object_name
    end
  end

  def options_with_prefix(position, options)
    prefix = name_prefix.to_s.dup
    if options[:index]
      prefix << "[#{options[:index]}]"
    elsif @auto_index
      prefix << "[#{@auto_index}]"
    end
    options.merge(:prefix => "#{prefix}[#{@method_name}(#{position}i)]")
  end

  def initialize(object_name, method_name, template_object, local_binding = nil, object = nil)
    @object_name2 = object_name
    @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
    @template_object, @local_binding = template_object, local_binding
    @object = object
    if @object_name.sub!(/\[\]$/,"")
      if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:id_before_type_cast)
        @auto_index = object.id_before_type_cast
      else
        raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to id_before_type_cast: #{object.inspect}"
      end
    end
  end
  
end    


